import os
import json
from abc import abstractmethod

import numpy as np
import requests
from dateutil import parser

from investment.constants import (
    METRICS_CATEGORY, INDUSTRY_CATEGORY, STOCK_METRICS,
    INDEX_METRICS_FUNDAMENTAL, INDEX_METRICS_FINANCE, INDEX_STATICAL_FORMATS
)

__all__ = [
    "SAVE_JSON_DIR", "SAVE_INDEX_DIR", "SAVE_COMPANY_DIR",  # save lixinger json dir
    "TRangeParams", "OnceParams", "SampleParams",
    "CompanyFinanceRangePost", "CompanyFundamentalRangePost",
    "IndexFinanceRangePost", "IndexFundamentalRangePost", "IndexSamplePost",
    "find_url_result_series",
    ]

_MY_TOKEN = "55ac383c-5a42-4a9d-89ab-90753f96c6a4"
_LIXINGER_ROOT_URL = "https://open.lixinger.com/api/cn"
SAVE_JSON_DIR = os.path.join(os.getcwd().split("Jazz")[0], r"Jazz/datasets/json")
SAVE_INDEX_DIR = os.path.join(SAVE_JSON_DIR, "indexes")
SAVE_COMPANY_DIR = os.path.join(SAVE_JSON_DIR, "company")


def get_index_url(info_type: METRICS_CATEGORY):
    return f"{_LIXINGER_ROOT_URL}/index/{info_type}"


def get_company_url(info_type: METRICS_CATEGORY, industry_type: INDUSTRY_CATEGORY):
    if info_type != METRICS_CATEGORY.fundamental and info_type != METRICS_CATEGORY.finance:
        raise ValueError(f"can't support this MetricsCategory of Company, {info_type}")
    return f"{_LIXINGER_ROOT_URL}/company/{info_type}/{industry_type}"


def get_company_metrics(info_type: METRICS_CATEGORY, industry_type: INDUSTRY_CATEGORY):
    if info_type == METRICS_CATEGORY.fundamental:
        if industry_type == INDUSTRY_CATEGORY.insurance:
            return STOCK_METRICS.FUNDAMENTAL_INSURANCE_REQUEST.value
        else:
            return STOCK_METRICS.FUNDAMENTAL_REQUEST.value
    elif info_type == METRICS_CATEGORY.finance:
        return STOCK_METRICS.FINANCE_REQUEST.value
    else:
        raise ValueError(f"can't support this MetricsCategory of Company, {info_type}")


class UrlParams:
    def __init__(self):
        self.metrics_list = []

    @abstractmethod
    def generate_url_json(self) -> "json":
        pass

    @abstractmethod
    def generate_save_name(self) -> "str":
        pass


class TRangeParams(UrlParams):
    def __init__(self, start_date: "str 2020-03-01", end_date: str, stock_code: str, metrics_list: list):
        super(TRangeParams, self).__init__()
        self.start_date = start_date
        self.end_date = end_date
        self.stock_code = stock_code
        self.metrics_list = metrics_list

    def generate_url_json(self) -> "json":
        url_param = {
            "token": _MY_TOKEN,
            "startDate": self.start_date,
            "endDate": self.end_date,
            "stockCodes": [self.stock_code],
            "metricsList": self.metrics_list
        }
        return url_param

    def generate_save_name(self) -> "str":
        return f"{self.stock_code}_{self.start_date}_{self.end_date}_{self.metrics_list}.json"


class OnceParams(UrlParams):
    def __init__(self, stock_codes: list, metrics_list: list, date: str = "latest"):
        super().__init__()
        self.date = date
        self.stock_code = stock_codes
        self.metrics_list = metrics_list

    def generate_url_json(self) -> "json":
        url_param = {
            "token": _MY_TOKEN,
            "date": self.date,
            "stockCodes": self.stock_code,
            "metricsList": self.metrics_list
        }
        return url_param

    def generate_save_name(self) -> "str":
        return f"{self.stock_code}_{self.date}_{self.metrics_list}.json"


class SampleParams(UrlParams):
    def __init__(self, stock_codes: list):
        super().__init__()
        self.stock_code = stock_codes

    def generate_url_json(self) -> "json":
        url_param = {
            "token": _MY_TOKEN,
            "date": "latest",
            "stockCodes": self.stock_code,
        }
        return url_param

    def generate_save_name(self) -> "str":
        return f"{self.stock_code}_samples.json"


class UrlPost:
    def __init__(self, url, url_params, save_dir, save_name=None):
        self.url = url
        self.token = _MY_TOKEN
        assert isinstance(url_params, UrlParams)
        self.url_params = url_params
        self.result = None
        self.save_name = save_name if save_name is not None else self.url_params.generate_save_name()
        self.save_dir = save_dir

    def post_get_result(self) -> "json":
        result = requests.post(url=self.url, json=self.url_params.generate_url_json())
        result_json = result.json()
        if not result_json.get('message') == 'success':
            raise ValueError("Post Failed" + result.text)
        print(result_json)
        self.result = result_json
        return result_json

    def have_saved(self) -> bool:
        return os.path.isfile(os.path.join(self.save_dir, self.save_name))

    def set_save_name(self, save_name):
        self.save_name = save_name

    def save_to_json(self):
        if not os.path.exists(self.save_dir):
            os.mkdir(self.save_dir)
        file_path = os.path.join(self.save_dir, self.save_name)
        with open(file_path, 'w') as file_handle:
            json.dump(self.result, file_handle)

    def load_json(self, json_path: str = None):
        file_path = json_path if json_path else os.path.join(self.save_dir, self.save_name)
        with open(file_path, 'r') as file_handle:
            self.result = json.load(file_handle)

    def parse_result(self) -> "time_list, result_dict dict-np.array":
        # node: from start_date to end date
        time_series = []
        result_dict = {}
        for ky in self.url_params.metrics_list:
            result_dict[ky] = []
        for res in self.result.get('data'):  # time from end to start
            time_series.append(parser.parse(res['date']).strftime("%Y-%m-%d"))
            for ky in self.url_params.metrics_list:
                metrics_format = ky.split(".")
                nested_value = res
                for i in range(len(metrics_format)):
                    if metrics_format[i] not in nested_value.keys():
                        print(metrics_format[i], "not found in", metrics_format, res['date'])
                        nested_value = np.nan
                        break
                    else:
                        nested_value = nested_value[metrics_format[i]]
                result_dict[ky].append(nested_value)
        for ky in self.url_params.metrics_list:
            result_dict[ky] = np.array(result_dict[ky][::-1])
        return time_series[::-1], result_dict


class IndexFundamentalRangePost(UrlPost):
    def __init__(self, start_date: "str 2020-03-01", end_date: str, stock_code: str):
        params = TRangeParams(start_date, end_date, stock_code, INDEX_METRICS_FUNDAMENTAL.REQUEST.value)
        save_name = f"{stock_code}_{start_date}_{end_date}_{INDEX_METRICS_FUNDAMENTAL.NAME.value}.json"
        super().__init__(url=get_index_url(info_type=METRICS_CATEGORY.fundamental), url_params=params,
                         save_dir=os.path.join(SAVE_INDEX_DIR, stock_code), save_name=save_name)

    def parse_result(self) -> "time_list, result_dict dict-np.array":
        time_series, result_dict = super().parse_result()
        original_keys = list(result_dict.keys())
        for ky in original_keys:
            if len(ky.split(".")) == 3:
                granularity = result_dict[ky]
                for form in INDEX_STATICAL_FORMATS:
                    result_dict[ky+":"+form] = np.array([value[form] for value in granularity])
                result_dict.pop(ky)
                # metrics = ky.split('.')
                result_dict[ky] = np.array(result_dict[ky+":cv"])
        return time_series, result_dict


def check_finance_date(date):
    """ unneeded to do, because lixinger api can range from start_date to end_date"""
    return date[-5:] == "03-31" or date[-5:] == "06-30" or date[-5:] == "09-30" or date[-5:] == "12-31"


class IndexFinanceRangePost(UrlPost):
    """由于每个季度的最后一天为财报日，请确保传入正确的日期，例如：2017-03-31、2017-06-30、2017-09-30、2017-12-31 或 latest"""
    """invalid price metrics url不对"""
    def __init__(self, start_date: "str", end_date: str, stock_code: str):
        params = TRangeParams(start_date, end_date, stock_code, INDEX_METRICS_FINANCE.REQUEST.value)
        save_name = f"{stock_code}_{start_date}_{end_date}_{INDEX_METRICS_FINANCE.NAME.value}.json"
        super().__init__(url=get_index_url(METRICS_CATEGORY.finance), url_params=params,
                         save_dir=os.path.join(SAVE_INDEX_DIR, stock_code), save_name=save_name)


class IndexSamplePost(UrlPost):
    def __init__(self, stock_code: list):
        save_name = f"{stock_code}_index_samples.json"
        super().__init__(url=get_index_url(METRICS_CATEGORY.sample), url_params=SampleParams(stock_code),
                         save_dir=os.path.join(SAVE_INDEX_DIR, stock_code[0]), save_name=save_name)

    def parse_result(self) -> "latest, dict":
        stock_sample_dict = {}
        # support multi indexes
        for res in self.result.get('data'):
            sample_list = [cons['stockCode'] for cons in res['constituents']]
            stock_sample_dict[res['stockCode']] = sample_list
        return "latest", stock_sample_dict


class CompanyFundamentalRangePost(UrlPost):
    def __init__(self, start_date: "str 2020-03-01", end_date: str, stock_code: str, company_type: INDUSTRY_CATEGORY):
        self.metrics_list = get_company_metrics(METRICS_CATEGORY.fundamental, company_type)
        params = TRangeParams(start_date, end_date, stock_code, self.metrics_list)
        save_name = f"{stock_code}_{start_date}_{end_date}_{STOCK_METRICS.FUNDAMENTAL_NAME.value}.json"
        super().__init__(url=get_company_url(METRICS_CATEGORY.fundamental, company_type), url_params=params,
                         save_dir=os.path.join(SAVE_COMPANY_DIR, stock_code), save_name=save_name)


class CompanyFinanceRangePost(UrlPost):
    def __init__(self, start_date: "str 2020-03-01", end_date: str, stock_code: str, company_type: INDUSTRY_CATEGORY):
        self.metrics_list = get_company_metrics(METRICS_CATEGORY.finance, company_type)
        params = TRangeParams(start_date, end_date, stock_code, self.metrics_list)
        save_name = f"{stock_code}_{start_date}_{end_date}_{STOCK_METRICS.FINANCE_REQUEST_NAME.value}.json"
        super().__init__(url=get_company_url(METRICS_CATEGORY.finance, company_type), url_params=params,
                         save_dir=os.path.join(SAVE_COMPANY_DIR, stock_code), save_name=save_name)


def find_url_result_series(url_post, json_path: str = None) -> "time, metrics_dict":
    assert isinstance(url_post, UrlPost)
    if json_path:
        url_post.load_json(json_path)
    elif url_post.have_saved():
        url_post.load_json()
    else:
        url_post.post_get_result()
        url_post.save_to_json()
    time_series, metrics_dict = url_post.parse_result()
    return time_series, metrics_dict


if __name__ == "__main__":
    # my_url = IndexFundamentalRangePost("2020-03-31", "2022-05-24", '399995')
    # res_json = my_url.post_get_result()
    # my_url.save_to_json()
    # my_url.load_json()
    my_url = IndexFinanceRangePost("2020-03-31", "2022-05-24", '000300')
    res_json = my_url.post_get_result()
    my_url.save_to_json()
    # my_url = IndexSamplePost(['000300'])
    # res_json = my_url.post_get_result()
    # my_url.save_to_json()
    # my_url = CompanyFundamentalRangePost("2011-08-27", "2022-05-24", '000963', INDUSTRY_CATEGORY.non_finance)
    # res_json = my_url.post_get_result()
    # my_url.save_to_json()
    # my_url = CompanyFinanceRangePost("2011-08-27", "2022-05-24", '000963', INDUSTRY_CATEGORY.non_finance)
    # res_json = my_url.post_get_result()
    # my_url.save_to_json()
    # print(res_json)
    my_url.load_json()
    date_series, result_series = my_url.parse_result()
    import matplotlib.pyplot as plt
    plt.plot(date_series[::30], result_series[INDEX_METRICS_FUNDAMENTAL.PE.value][::30])
    plt.xticks(rotation=70)
